package br.cn.evo.controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

import br.cn.evo.model.Solution;
import br.cn.evo.util.Constants;
import br.cn.evo.util.EnumTypes;
import br.cn.evo.util.EnumTypes.AdaptativeStep;
import br.cn.evo.util.EnumTypes.Function;
import br.cn.evo.util.EnumTypes.Result;
import br.cn.evo.util.EnumTypes.SelectionType;
import br.cn.evo.util.GlobalParameters;
import br.cn.evo.util.IO;

public class EvolutionaryProgramming {
	static Random r;
	public static double min = Double.MAX_VALUE;
	public static double max = Double.MIN_VALUE;
	
	public static Solution bestSol = new Solution();
	
	
	public static double[] doEvolutionaryProgramming(Function function, Result local, AdaptativeStep adp, SelectionType sel,
			int numberParametersSolution, int populationSize, int numberIteractions, int maxPopulation, int numberGroup){
		
		ArrayList<Solution> allPopulation = new ArrayList<Solution>();
		double[] bestGenStep = new double[numberIteractions];
		
		bestSol = new Solution();

		for (int i = 0; i < populationSize; i++) {
			Solution s = EvolutionaryProgramming.initializeSolution(function, populationSize, numberParametersSolution);
			Solution o = EvolutionaryProgramming.createInitialOffspring(function, adp, s);

			allPopulation.add(s); 
			allPopulation.add(o);
			
			System.out.println("\n\nInitial Soltion");
			for (int j = 0; j < s.getArrayValues().length; j++) {
				System.out.print(s.getArrayValues()[j]+" | ");
			}
			System.out.println("\nPeso: "+s.getPeso());
			System.out.println("Passageiros: "+s.getPassageiros());
			System.out.println("Area Asa: "+s.getAreaAsa());
			System.out.println("Best Fitness : "+s.getFitness());
			
			
			System.out.println("\n\nInitial Offspring");
			for (int j = 0; j < o.getArrayValues().length; j++) {
				System.out.print(o.getArrayValues()[j]+" | ");
			}
			System.out.println("\nPeso: "+o.getPeso());
			System.out.println("Passageiros: "+o.getPassageiros());
			System.out.println("Area Asa: "+o.getAreaAsa());
			System.out.println("Best Fitness : "+o.getFitness());
		}

		Solution[] ret = new Solution[allPopulation.size()];
		allPopulation.toArray(ret);

		for (int i = 0; i < numberIteractions; i++) {
			ret = EvolutionaryProgramming.doStep(function, local, adp, sel, ret, numberGroup, maxPopulation, i, numberIteractions);
			bestGenStep[i] = (local == Result.LocalMinimum) ? EvolutionaryProgramming.min : EvolutionaryProgramming.max;
			
			System.out.println(i+" - Fitness : "+bestGenStep[i]+"\t\t\tPopulation : "+Selection.maxPop);
		}

		System.out.print("\n\nBest Solution : ");
		for (int i = 0; i < EvolutionaryProgramming.bestSol.getArrayValues().length; i++) {
			System.out.print(EvolutionaryProgramming.bestSol.getArrayValues()[i]+" | ");
		}
		
		System.out.println("\nPeso: "+EvolutionaryProgramming.bestSol.getPeso());
		System.out.println("Passageiros: "+EvolutionaryProgramming.bestSol.getPassageiros());
		System.out.println("Area Asa: "+EvolutionaryProgramming.bestSol.getAreaAsa());
		System.out.println("\nBest Fitness : "+EvolutionaryProgramming.bestSol.getFitness());
		
		double[] retorno = new double[bestGenStep.length];
		retorno = bestGenStep;
		
		return retorno;
	}
	
	public static Solution[] doStep(Function function, Result result, AdaptativeStep adp, SelectionType sel, 
			Solution[] population, int numberGroup, int maxPopulation, int actualIteraction, int numberIteraction){
		
		Solution[] nextGenParent = null;
		if(maxPopulation != 0){
			switch (sel) {
			case Tournament:
				nextGenParent = Selection.newGenerationMaxPopTournament(result, population, numberGroup, maxPopulation);
				break;
			case Elitism:
				nextGenParent = Selection.newGenerationMaxPopElitism(result, population, maxPopulation);
				break;
			default:
				nextGenParent = null;
			}
		}else{
			switch (sel) {
			case Tournament:
				nextGenParent = Selection.newGenerationTournament(result, population, maxPopulation);
				break;
			case Elitism:
				nextGenParent = Selection.newGenarationElistism(result, population);
				break;
			default:
				nextGenParent = null;
			}
		}
		//gerar offspring
		Solution[] nextGenOffspring = new Solution[nextGenParent.length];
		//double alpha = 1.0 - ((double) actualIteraction/numberIteraction);
		double alpha = 0.5;
		for (int i = 0; i < nextGenParent.length; i++) {
			nextGenOffspring[i] = createOffspring(function, adp, nextGenParent[i], alpha);
		}
		
		Solution[] ret = new Solution[nextGenParent.length+nextGenOffspring.length];
		
		for (int i = 0; i < nextGenParent.length; i++) {
			ret[i] = nextGenParent[i];
		}
		
		for (int i = 0, j = nextGenParent.length; i < nextGenOffspring.length; i++, j++) {
			ret[j] = nextGenOffspring[i];
		}
		
		//ver melhor step;
		for (int i = 0; i < ret.length; i++) {
			if(result == Result.LocalMinimum){
				if(ret[i].getFitness() <= min){
					min = ret[i].getFitness();
					bestSol = ret[i];
					//bestSol.setRemainingFuel(Fitness.remainingFuel);
				}
			}else{
				if(ret[i].getFitness() >= max){
					max = ret[i].getFitness();
					bestSol = ret[i];
				}
			}
		}
		return ret;
	}
	
	public static Solution initializeSolution(Function function, int numberSolution, int numberValues){
		r = new Random();
		Solution sol = new Solution(numberValues);
		
		//Randomiza e preenche os valores dos vetores Value e Step
		for (int i = 0; i < numberValues; i++) {
			//Pega os m�ximos e minimos pra cada parametro
			double max = Fitness.getMax(function, i);
			double min = Fitness.getMin(function, i);
			
			//randomiza os valores dentro do minimo e maximo definidos
			sol.getArrayValues()[i] = (max-min)*r.nextDouble() + min;
			sol.getArraySteps()[i] = (max-min)*r.nextDouble() + min;
			
			//randomiza os parametros globais
			sol.setTanqueCombustivel(GlobalParameters.fuelCapacity);
			sol.setAreaAsa(Constants.wingArea);
			//Entre 10 e 176 passageiros
			sol.setTremPouso(r.nextBoolean());
			sol.setPassageiros(r.nextInt(167) + 10);
			sol.setPeso( Constants.weight + GlobalParameters.fuelCapacity + sol.getPassageiros()*(63.6 + 32) );
		}
		//Calcula o Fitness da Solu��o
		double fitness = Fitness.getFitness(function, sol.getArrayValues(), sol.isTremPouso(), sol.getTanqueCombustivel(),
				 sol.getPassageiros(), sol.getPeso(), sol.getAreaAsa());
		sol.setFitness(fitness);
		sol.setRemainingFuel(Fitness.remainingFuel);
		
		return sol;
	}
	
	public static Solution createOffspring(Function function, AdaptativeStep adp, Solution parent, double alpha){
		Solution offspring = new Solution();
		
		//Muta��o dos Valores e step
		double[] mutatedValues = new double[parent.getArrayValues().length];
		double[] mutatedSteps = new double[parent.getNumberValues()];
		
		for (int i = 0; i < mutatedValues.length; i++) {
			//Se for adaptativo, gera um novo step para o filho
			//Caso n�o, repete o anterior
			if(adp == AdaptativeStep.Normal)
				mutatedSteps[i] = parent.getArraySteps()[i];
			if(adp == AdaptativeStep.Lognormal)
				mutatedSteps[i] = Mutation.doStepMutationLognormal(function, parent.getArraySteps()[i]);
			if(adp == AdaptativeStep.Adaptative)
				mutatedSteps[i] = Mutation.doStepMutationAdaptive(function, parent.getArraySteps()[i], alpha, i);
			
			//Cria o valor mutado
			mutatedValues[i] = Mutation.doValueMutation(function, parent.getArrayValues()[i], 
					mutatedSteps[i], i);
		}
		
		//randomiza os parametros globais
		offspring.setTanqueCombustivel(GlobalParameters.fuelCapacity);
		offspring.setAreaAsa(Constants.wingArea);
		//Entre 10 e 176 passageiros
		offspring.setTremPouso(r.nextBoolean());
		offspring.setPassageiros(r.nextInt(167) + 10);
		offspring.setPeso(parent.getPeso());
		
		offspring.setArraySteps(mutatedSteps);
		offspring.setArrayValues(mutatedValues);
		offspring.setFitness(Fitness.getFitness(function, offspring.getArrayValues(), offspring.isTremPouso(), parent.getTanqueCombustivel(),
				parent.getPassageiros(), offspring.getPeso(), offspring.getAreaAsa()));	
		offspring.setGeneration(parent.getGeneration());//Os filhos pertencem a mesma gera��o que os pais
		offspring.setNumberValues(parent.getNumberValues());
		
		offspring.setRemainingFuel(Fitness.remainingFuel);
		return offspring;
	}
	
	public static Solution createInitialOffspring(Function function, AdaptativeStep adp, Solution parent){
		Solution offspring = new Solution();
		//Muta��o dos Valores e step
		double[] mutatedValues = new double[parent.getArrayValues().length];
		double[] mutatedSteps = new double[parent.getNumberValues()];
		for (int i = 0; i < mutatedValues.length; i++) {
			//Se for adaptativo, gera um novo step para o filho
			//Caso n�o, repete o anterior
			double alpha = 0.5;
			if(adp == AdaptativeStep.Normal)
				mutatedSteps[i] = parent.getArraySteps()[i];
			if(adp == AdaptativeStep.Lognormal)
				mutatedSteps[i] = Mutation.doStepMutationLognormal(function, parent.getArraySteps()[i]);
			if(adp == AdaptativeStep.Adaptative)
				mutatedSteps[i] = Mutation.doStepMutationAdaptive(function, parent.getArraySteps()[i], alpha, i);
			
			//Cria o valor mutado
			mutatedValues[i] = Mutation.doValueMutation(function, parent.getArrayValues()[i], 
					mutatedSteps[i], i);
			
		}
		
		//randomiza os parametros globais
		offspring.setTanqueCombustivel(GlobalParameters.fuelCapacity);
		offspring.setAreaAsa(Constants.wingArea);
		//Entre 10 e 176 passageiros
		offspring.setTremPouso(r.nextBoolean());
		offspring.setPassageiros(r.nextInt(167) + 10);
		offspring.setPeso(parent.getPeso());
				

		offspring.setArraySteps(mutatedSteps);
		offspring.setArrayValues(mutatedValues);
		offspring.setFitness(Fitness.getFitness(function, offspring.getArrayValues(), offspring.isTremPouso(), parent.getTanqueCombustivel(),
				parent.getPassageiros(), offspring.getPeso(), offspring.getAreaAsa()));	
		offspring.setGeneration(parent.getGeneration());//Os filhos pertencem a mesma gera��o que os pais
		offspring.setNumberValues(parent.getNumberValues());
		
		offspring.setRemainingFuel(Fitness.remainingFuel);
		
		return offspring;
	}

}
